<template>
    <div class="table-wrapper" ref="tableWrapper">
        <div class="header-wrapper" ref="headerWrapper" :style="{ width: `calc(100% - ${scrollbarWidth}px)` }">           
            <div class="header">
                <div :class="['header-checkbox','fixed-left']" v-if="isCheckBox">
                    <customCheckbox :indeterminate="isIndeterminate" :checked="isAllSelected" @change="toggleAllSelection">
                    </customCheckbox>
                </div>
                <div v-for="item in tableColumns" :class="['cell', {
                    'fixed-left': item.fixed === 'left',
                    'fixed-right': item.fixed === 'right'
                }]" :style="{
                    'width': item.computedWidth, left: item.fixed === 'left' ? getFixedOffset(item, 'left') : undefined,
                    right: item.fixed === 'right' ? getFixedOffset(item, 'right') : undefined
                }">                    
                    <slot :name="`header-${item.key}`">{{ item.name }}</slot>
                </div>
            </div>
        </div>
        <div class="body-wrapper" ref="bodyWrapper" @scroll="handleScroll">
            <div class="virtual-list-phantom" :style="{ height: totalHeight + 'px', width: tableWidth + 'px' }"></div>
            <div class="virtual-list" :style="{ transform: `translate3d(0, ${offset}px, 0)` }">
                <div v-for="(rowData, index) in visibleData" :key="index" class="table-row">                    
                    <div class="row">
                        <div :class="['row-checkbox','fixed-left']" v-if="isCheckBox">
                            <customCheckbox :checked="isChecked(index)" @change="toggleSelection(index)"></customCheckbox>
                        </div>
                        <span v-for="item in tableColumns" :class="['cell', {
                            'fixed-left': item.fixed === 'left',
                            'fixed-right': item.fixed === 'right'
                        }]" :style="{
                            width: item.computedWidth, left: item.fixed === 'left' ? getFixedOffset(item, 'left') : undefined,
                            right: item.fixed === 'right' ? getFixedOffset(item, 'right') : undefined
                        }">
                            <slot :name="`column-${item.key}`" :rowData="rowData">{{ rowData[item.key] }}</slot>
                        </span>
                    </div>
                </div>
            </div>
        </div>
        <div class="fixed-box-left" :style="{height: '100%' , width: leftFixedWidthSum + 'px'}"></div>
        <div class="fixed-box-right" :style="{height: '100%', width: rightFixedWidthSum + scrollbarWidth +'px'}"></div>
    </div>
</template>

<script setup lang="ts">
import customCheckbox from '@/components/CustomCheckbox/index.vue';
import type { TableColumns } from './types';
//滚动事件节流处理
import { throttle } from 'lodash-es';

interface VirtualTableProps {
    tableColumns: TableColumns[];
    data: Record<string, any>[];
    isCheckBox?: boolean;
};

const props = withDefaults(defineProps<VirtualTableProps>(), {
    isCheckBox: false,
});
const tableWrapper = ref<HTMLElement | null>();
const headerWrapper = ref<HTMLElement | null>();
const bodyWrapper = ref<HTMLElement | null>();


//***************虚拟滚动************
const itemHeight = 40;
const bufferSize = 2;
// const itemWidth = computed(() => columns.value.length * 200);

const state = reactive({
    visibleStartIndex: 0,  // 可见数据起始索引
    visibleEndIndex: 20,   // 可见数据结束索引
});


// 总高度计算
const totalHeight = computed(() => {
    return 1 + props.data.length * itemHeight;
});

// 可见数据计算
const visibleData = computed(() =>
    props.data.slice(state.visibleStartIndex, state.visibleEndIndex)
);

let lastScrollTop = 0;
let lastScrollLeft = 0;

const calcScrollbarWidth = () => {
    if (bodyWrapper.value) {
        scrollbarWidth.value = bodyWrapper.value.offsetWidth - bodyWrapper.value.clientWidth;
        calculateColumnWidths(scrollbarWidth.value);
    }
    //判断是否有横向滚动条
    if (bodyWrapper.value && bodyWrapper.value.scrollWidth > bodyWrapper.value.clientWidth) {
        if(scrollState.isAtLeft && scrollState.isAtRight){
            scrollState.isAtLeft = true;
            scrollState.isAtRight = false;
        }
    } else {
        scrollState.isAtLeft = true;
        scrollState.isAtRight = true;
    }
};
//动态计算列的宽度
const tableWidth = ref<number>(0);
const calculateColumnWidths = (val: number) => {

    if (!tableWrapper.value) return;
    const containerWidth = tableWrapper.value.clientWidth - val - (props.isCheckBox ? 26 : 1);
    const totalMinWidth = props.tableColumns.reduce((sum, col) => sum + parseInt(col.minWidth, 10), 0);
    if (containerWidth > totalMinWidth) {
        const remainingSpace = containerWidth - totalMinWidth;
        // 按各列minWidth的比例分配剩余空间
        props.tableColumns.forEach(col => {
            const minWidth = parseInt(col.minWidth, 10);
            const ratio = minWidth / totalMinWidth;
            const addedWidth = remainingSpace * ratio;
            col.computedWidth = `${minWidth + addedWidth}px`;
        });
    } else {
        // 使用最小宽度
        props.tableColumns.forEach(col => {
            col.computedWidth = col.minWidth;
        });
    }

    // 更新表格总宽度
    tableWidth.value = containerWidth > totalMinWidth ? containerWidth : totalMinWidth;
};

const handleScroll = throttle((event: Event) => {
    const target = event.target as HTMLElement;
    const { scrollTop, scrollLeft } = target;

    if (lastScrollTop !== scrollTop && bodyWrapper.value) {
        // 计算当前滚动位置的索引
        const currentStartIndex = Math.floor(scrollTop / itemHeight);
        const visibleItemCount = Math.ceil(bodyWrapper.value.clientHeight / itemHeight);

        // 更新可见数据起始和结束索引
        state.visibleStartIndex = Math.max(0, currentStartIndex - bufferSize);
        state.visibleEndIndex = Math.min(props.data.length, currentStartIndex + visibleItemCount + bufferSize);
        //记录上一次的滚动条y轴位置 
        lastScrollTop = scrollTop;
    }
    if(lastScrollLeft !== scrollLeft && headerWrapper.value){
        // 新增水平位置判断
        checkScrollPosition(target);
        //body和header横向滚动同步    
        headerWrapper.value.scrollLeft = scrollLeft;
        //记录上一次的滚动条y轴位置 
        lastScrollLeft = scrollLeft;
    }
}, 16);

// 偏移量计算
const offset = computed(() => {
    return state.visibleStartIndex * itemHeight;
});

//-------复选框状态----------
const selectedMap = ref<Set<number>>(new Set()); // 存储数据唯一ID
// 判断选中状态
function isChecked(index: number): boolean {
    const actualIndex = state.visibleStartIndex + index;
    return selectedMap.value.has(actualIndex);

}
// 单行切换
function toggleSelection(index: number) {
    const actualIndex = state.visibleStartIndex + index;
    selectedMap.value.has(actualIndex) ? 
        selectedMap.value.delete(actualIndex) : 
        selectedMap.value.add(actualIndex);
}

// 全选状态计算
const isAllSelected = computed(() => {
    if (props.data.length === 0) return false;
    return props.data.every((_, index) => 
        selectedMap.value.has(index)
    );
});
// 半选中状态
const isIndeterminate = computed(() => {
    if (props.data.length === 0) return false;
    return selectedMap.value.size > 0 && selectedMap.value.size < props.data.length;
});
// 全选操作
function toggleAllSelection() {
    if (props.data.length === 0) return false;
    const allKeys = new Set(props.data.map((_, index) => index));
    
    if (isAllSelected.value) {
        selectedMap.value.clear();
    } else {
        allKeys.forEach(key => selectedMap.value.add(key));
    }
}
//冻结列处理
// 计算冻结列定位偏移量
const fixedLeftColumns = computed(() =>
    props.tableColumns.filter(c => c.fixed === 'left')
)
const fixedRightColumns = computed(() =>
    props.tableColumns.filter(c => c.fixed === 'right')
)

const getFixedOffset = (col: TableColumns, direction: 'left' | 'right') => {
    const columns = direction === 'left' ? fixedLeftColumns.value : fixedRightColumns.value;
    const index = columns.findIndex(c => c.key === col.key);

    if (direction === 'left') {
        // 左侧固定列：从左侧开始累加宽度
        return columns
            .slice(0, index)
            .reduce((sum, c) => sum + parseInt(c.computedWidth || c.minWidth, 10), 0) + (props.isCheckBox ? 25 : 0) + 'px';
    } else {
        // 右侧固定列：从右侧开始累加宽度
        return columns
            .slice(index + 1) // 从当前列的下一个列开始
            .reduce((sum, c) => sum + parseInt(c.computedWidth || c.minWidth, 10), 0) + 'px';
    }
};
//计算冻结列的总宽度，初始化的时候计算
const leftFixedWidthSum = ref(0);
const rightFixedWidthSum = ref(0);
const calculateFixedWidths = () => {
    const leftFixedColumns = props.tableColumns.filter(col => col.fixed === 'left');
    const rightFixedColumns = props.tableColumns.filter(col => col.fixed === 'right');

    leftFixedWidthSum.value = leftFixedColumns.reduce((sum, col) => {
        return sum + parseInt(col.minWidth, 10);
    }, 0);

    if(props.isCheckBox){
        leftFixedWidthSum.value += 25;
    }

    rightFixedWidthSum.value = rightFixedColumns.reduce((sum, col) => {
        return sum + parseInt(col.minWidth, 10);
    }, 0);
};
// 新增响应式状态
const scrollState = reactive({
    isAtLeft: true,
    isAtRight: false
});

const checkScrollPosition = (element: HTMLElement) => {
    const { scrollLeft, scrollWidth, clientWidth } = element;
    const epsilon = 1; // 容错值，避免精度问题    
    
    // 判断是否在最左侧
    scrollState.isAtLeft = scrollLeft <= epsilon;
    
    // 判断是否在最右侧
    scrollState.isAtRight = scrollLeft + clientWidth >= scrollWidth - epsilon;    
};
// 初始化和更新滚动处理器
// 添加缓存变量
const scrollbarWidth = ref(0);
let resizeObserver: ResizeObserver;
onMounted(() => {
    if (bodyWrapper.value) {
        // 初始化时计算可见数据起始和结束索引
        const visibleItemCount = Math.ceil(bodyWrapper.value.clientHeight / itemHeight);
        state.visibleEndIndex = visibleItemCount + bufferSize;
        //动态调整 header 宽度
        resizeObserver = new ResizeObserver(calcScrollbarWidth);
        resizeObserver.observe(bodyWrapper.value);
        calculateFixedWidths();
    }
});

onUnmounted(() => {
    if (resizeObserver) resizeObserver.disconnect();
    handleScroll.cancel();
});

// 监听data变化更新滚动
// watch(data.value, () => {
//     handleScroll({ target: bodyWrapper.value });
// });
</script>

<style scoped lang="scss">
.table-wrapper {
    position: relative;
    --row-height: 40px;
    width: 100%;
    // max-width: 1910px;
    height: 100%;
    background-color: var(--el-bg-color-overlay);
    border-radius: var(--el-border-radius-base);
    white-space: nowrap;

    .header-wrapper {
        border-bottom: 1px solid var(--el-border-color-lighter);
        overflow: hidden;
        /* 防止水平滚动条出现在header */
        font-size: 13px;
        font-weight: bold;
        color: var(--el-text-color-primary);

        .header-checkbox {
            width: 25px;
            display: inline-block;
            left: 5px;
        }
        .header{
            height: var(--row-height);
            line-height: calc(var(--row-height) - 1px);
        }
    }

    .body-wrapper {
        width: 100%;
        overflow: auto;
        height: calc(100% - 32px);
        position: relative;
        font-size: 12px;

        .virtual-list-phantom {
            position: absolute;
            top: 0;
            left: 0;
            right: 0;
            z-index: -1;
        }

        .virtual-list {
            height: 100%;
            position: absolute;
            top: 0;
            left: 0;
            transform: translateZ(0);
            will-change: transform; // 启用 GPU 加速
        }

        .table-row {            
            border-bottom: 1px solid var(--el-border-color-lighter);

            .row-checkbox {
                left: 5px;
                width: 25px;
                display: inline-block;                
            };

            .row {                              
                color: var(--el-text-color-regular);
                height: var(--row-height);
                line-height: calc(var(--row-height) - 1px);
            }
            .row:hover{
                background-color: var(--el-fill-color);
                .fixed-left{
                    background-color: var(--el-fill-color);
                }
                .fixed-right{
                    background-color: var(--el-fill-color);
                }
            }
        }
    }
}

.cell {
    display: inline-block;
    padding-left: 10px;
    height: calc(var(--row-height) - 1px);
    vertical-align: middle; /* 垂直居中 */   
}

.fixed-left,
.fixed-right {
    position: sticky;
    background: var(--el-bg-color-overlay);    
}
.fixed-box-left{
    position: absolute;
    left: 0;
    top: 0;
    z-index: 3;
    pointer-events: none; /* 忽略鼠标事件 */
    box-shadow: 10px 0 10px -5px rgba(0,0,0,0.12);
    -webkit-box-shadow: 10px 0 10px -5px rgba(0,0,0,0.12); /* Safari/Chrome */
    -moz-box-shadow: 10px 0 10px -5px rgba(0,0,0,0.12); /* Firefox */
    opacity: v-bind('scrollState.isAtLeft ? 0 : 1'); 
    transition: opacity 0.2s;
}
.fixed-box-right{
    position: absolute;    
    right: 0;
    top: 0;
    z-index: 3;
    pointer-events: none; /* 忽略鼠标事件 */
    box-shadow: -10px 0 10px -5px rgba(0,0,0,0.12);
    -webkit-box-shadow: -10px 0 10px -5px rgba(0,0,0,0.12); /* Safari/Chrome */
    -moz-box-shadow: -10px 0 10px -5px rgba(0,0,0,0.12); /* Firefox */
    opacity: v-bind('scrollState.isAtRight ? 0 : 1');
    transition: opacity 0.2s;
}
</style>
